<?php
namespace Tcp\Controller;

define("TCP_SERVER_DEBUG", false);

use \Think\Controller;
use GuzzleHttp\json_encode;

class ServerController extends Controller
{

    public function run()
    {
        if (! IS_CLI) {
            exit();
        }
        
        $this->redisClear();
        
        $serv = new \swoole_server('0.0.0.0', 9911);
        $serv->set(array(
            'worker_num' => 8,
            'daemonize' => ! TCP_SERVER_DEBUG,
            'open_eof_check' => true,
            'open_eof_split' => true,
            'package_eof' => "\r\n",
            'heartbeat_check_interval' => 60,
            'heartbeat_idle_time' => 200
        ));
        $serv->on('connect', function ($serv, $fd)
        {
            $this->outputLog("Connect:$fd");
        });
        $serv->on('WorkerStart', function ($serv, $worker_id)
        {
            // 不是task进程（即为worker进程）
            if (! $serv->taskworker) {
                $serv->tick(1000, function () use($serv)
                {
                    $redis = $this->redisInit();
                    // 取出一个消息包进行处理，一个消息包包括消息接收者(必须)、消息内容(必须)、处理次数(非必须)
                    $msgPacket = $redis->lPop("socket_message");
                    if (! $msgPacket) {
                        return;
                    }
                    $msgPacket = json_decode($msgPacket, true);
                    if (! is_array($msgPacket)) {
                        $this->outputLog("Msg format error");
                        return;
                    }
                    if (empty($msgPacket['user']) || empty($msgPacket['msg']) || (isset($msgPacket['worked']) && $msgPacket['worked'] >= 5)) {
                        $this->outputLog("Msg parameter error");
                        return;
                    }
                    
                    $user = $msgPacket['user'];
                    $msg = $msgPacket['msg'];
                    
                    $fd = $redis->Get("socket_user_connection:$user");
                    if ($fd) {
                        // 消息钩子
                        if (! empty($msg['cmd'])) {
                            $cmd = $msg['cmd'];
                            if ($cmd == 'password_modified') {
                                $redis->Del("socket_user_connection:$user");
                                $redis->Del("socket_connection_user:$fd");
                                $token = $redis->Get("socket_user_token:$user");
                                $redis->Del("socket_token_user:$token");
                            }
                        }
                        
                        // 发送消息
                        if (! is_string($msg)) {
                            $msg = json_encode($msg, JSON_UNESCAPED_UNICODE);
                        }
                        $this->outputLog("Msg:$fd:$user:$msg");
                        $serv->send($fd, "$msg\r\n");
                    } else {
                        if (isset($msgPacket['worked'])) {
                            $msgPacket['worked'] ++;
                        } else {
                            $msgPacket['worked'] = 1;
                        }
                        $redis->rPush("socket_message", json_encode($msgPacket, JSON_UNESCAPED_UNICODE));
                    }
                });
            }
        });
        $serv->on('receive', function ($serv, $fd, $from_id, $data)
        {
            $this->outputLog("Receive:$fd:" . bin2hex($data));
            $data = str_replace("\r\n", '', $data);
            if (empty($data)) {
                return;
            }
            $this->outputLog("Receive:$fd:$data");
            $data = json_decode($data, true);
            if (! isset($data['cmd']) || ! (isset($data['args']))) {
                return;
            }
            $args = $data['args'];
            switch ($data['cmd']) {
                case 'login_account':
                    if ($userId = $this->login_account($args)) {
                        $token = $this->login_success($userId, $fd, $serv);
                    } else {
                        $this->outputLog("Account login failure:$fd");
                        $serv->send($fd, $this->getMsgString('login', array(
                            'status' => 'account_error'
                        )));
                    }
                    break;
                case 'login_token':
                    if ($userId = $this->login_token($args)) {
                        $token = $this->login_success($userId, $fd, $serv);
                    } else {
                        $this->outputLog("Token login failure:$fd");
                        $serv->send($fd, $this->getMsgString('login', array(
                            'status' => 'token_overdue'
                        )));
                    }
                    break;
                case 'report_issue_rf':
                case 'report_issue_bluetooth':
                    try {
                        // 获取客栈同步锁掌柜集群号
                        $hotelLogic = A('Common/Hotel', 'Logic');
                        $hotelInfo = $hotelLogic->getHotelByHotelId($args['hotelId']);
                        if (empty($hotelInfo[0]['communityno'])) {
                            return;
                        }
                        
                        // RPC通知锁掌柜增加记录
                        Vendor('phpRPC.phprpc_client');
                        $client = new \PHPRPC_Client(C('MANAGE_RFCARD_URL'));
                        $client->setEncryptMode(3);
                        $RPCData = array(
                            'flag' => 'uclbrt',
                            'time' => time(),
                            'communityNo'=> $hotelInfo[0]['communityno'],
                            'serialNum'=> $args['serialNum'],
                            'cardData'=> $args['cardData'],
                            'oldCardData'=> $args['oldCardData'],
                        );
                        $result = $client->reportWriteResult($RPCData);
                        
                        // 用户增加打印次数
                        $orderLogic = A('Common/Order', 'Logic');
                        $orderLogic->addRfCount($args['userRoomId']);
                    }
                    catch (\Exception $e) {
                        return;
                    }
                    break;
                case 'report_read_card':
                    sendWebsocketMsg($args['webId'], $data['cmd'], array(
                        'cardData' => $args['cardData'],
                        'isCancel' => $args['isCancel'],
                        'mac' => $args['mac'],
                    ));
                    break;
            }
        });
        $serv->on('close', function ($serv, $fd)
        {
            $redis = $this->redisInit();
            $userId = $redis->Get("socket_connection_user:$fd");
            if ($userId) {
                $redis->Del("socket_user_connection:$userId");
                $redis->Del("socket_connection_user:$fd");
            }
            $this->outputLog("Close:$fd:$userId");
        });
        
        $serv->start();
    }

    private function getMsgString($cmd, $args)
    {
        $msg = array();
        $msg['cmd'] = $cmd;
        $msg['args'] = $args;
        return json_encode($msg, JSON_UNESCAPED_UNICODE) . "\r\n";
    }

    private function login_account($args)
    {
        if (empty($args['username']) || empty($args['area']) || empty($args['password'])) {
            return 0;
        }
        $adminLoginModel = D('AdminLogin');
        $adminLogin = $adminLoginModel->getInfoByMobile($args['username'], $args['area']);
        if (! empty($adminLogin) && $adminLogin['password'] == hash('sha256', $args['password'])) {
            return $adminLogin['id'];
        }
        return 0;
    }

    private function login_token($args)
    {
        if (empty($args['token'])) {
            return 0;
        }
        $redis = $this->redisInit();
        $userId = $redis->Get("socket_token_user:{$args['token']}");
        if ($userId) {
            return $userId;
        }
        return 0;
    }

    private function login_success($userId, $fd, $serv)
    {
        $token = sha1($userId . $fd . time());
        $redis = $this->redisInit();
        
        // 判断是否已登录，如果已登录则清掉登录信息并向旧连接发送被迫下线消息，但服务器不断开连接以防止客户端在消息还没收好就中断了
        $oldFd = $redis->Get("socket_user_connection:$userId");
        if ($oldFd) {
            $redis->Del("socket_connection_user:$oldFd");
            $fdinfo = $serv->connection_info($fd);
            $ip = isset($fdinfo['remote_ip']) ? $fdinfo['remote_ip'] : 'unknown';
            $this->outputLog("Offline:$oldFd:$ip");
            $serv->send($oldFd, $this->getMsgString('offline', array(
                'ip' => $ip,
                'time' => date('Y-m-d H:i:s')
            )));
        }
        
        $redis->Set("socket_user_connection:$userId", $fd);
        $redis->Set("socket_connection_user:$fd", $userId);
        if ($oldToken = $redis->Get("socket_user_token:$userId")) {
            $redis->Del("socket_token_user:$oldToken");
        }
        $redis->Set("socket_user_token:$userId", $token);
        $redis->Set("socket_token_user:$token", $userId);
        $redis->expire("socket_token_user:$token", 7 * 24 * 3600);

        $this->outputLog("Login success:$fd:$userId");
        $serv->send($fd, $this->getMsgString('login', array(
            'status' => 'success',
            'token' => $token,
            'user_identity' => md5($userId + M_PI)
        )));
    }

    private function redisInit($dbIndex = 11)
    {
        $redis = new \Redis();
        $redis->connect(C('REDIS_SERVERIP'), C('REDIS_SERVERPORT'), 30);
        $redis->auth(C('REDIS_AUTH_PASSWORD'));
        $redis->select($dbIndex);
        return $redis;
    }
    
    private function redisClear()
    {
        $redis = $this->redisInit();
        $keys = $redis->Keys('socket_user_connection:*');
        $redis->Del($keys);
        $keys = $redis->Keys('socket_connection_user:*');
        $redis->Del($keys);
        $keys = $redis->Keys('socket_user_token:*');
        $redis->Del($keys);
        $keys = $redis->Keys('socket_token_user:*');
        $redis->Del($keys);
    }
    
    private function outputLog($info)
    {
        $info = date('Y-m-d H:i:s') . " $info\n";
        error_log($info, 3, LOG_PATH . 'TCP_' . date('Y_m_d') . '.log');
        if (TCP_SERVER_DEBUG) {
            echo $info;
        }
    }
}
